using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Reflection;
using XMas;


namespace XMasTest
{
    /// <summary>
    /// Class to pass data to xmas by reflexion
    /// </summary>
    public class TestReflexion
    {
        /// <summary>
        /// Our possibles states
        /// </summary>
        public enum TestState
        {
            State_Alive,
            State_Dead,
            State_Reviving
        };

        /// <summary>
        /// Value from 0 (idle) to 1 (run)
        /// </summary>
        private float       _fRunPercent;
        /// <summary>
        /// Current state
        /// </summary>
        private TestState   _iState;

        /// <summary>
        /// Property for _fRunPercent, Set reflexion value to Speed on crossfade node in graph
        /// </summary>
        public float Speed
        {
            get { return _fRunPercent; }
            set { _fRunPercent = value; }
        }

        /// <summary>
        /// Property for _iState, Set reflexion value to State on blendlist node in graph
        /// </summary>        
        public TestState State
        {
            get { return _iState; }
            set { _iState = value; }           
        }


        public TestReflexion()
        {
            _fRunPercent = 0;
            _iState = TestState.State_Alive;
        }
    }


    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager  graphics;
        SpriteBatch            spriteBatch;
        SpriteFont             spriteFont;
        Model                  initialModel;
        XMas.XMAnimationSystem _animationSystem;        // Our xmas !!
        TestReflexion          testReflexion;           // Class to pass data to xmas by reflexion
        bool                   bWaitingToAnimEnd;       // If shall we wait to anim end event

        // FPS
        TimeSpan _fElapsedTime = TimeSpan.Zero;
        float _fFrameCounter = 0;
        float _fFPS = 0;

        // Camera stuff
        static float DefaultcameraArc = -4f;
        static float DefaultcameraRotation = 180.00f;
        static float DefaultcameraDistance = 210.0f;
        static Vector3 DefaultcameraPan = new Vector3(0f, -75.0f, 0);
        float cameraArc = DefaultcameraArc;
        float cameraRotation = DefaultcameraRotation;
        float cameraDistance = DefaultcameraDistance;
        Vector3 cameraPan = new Vector3(DefaultcameraPan.X, DefaultcameraPan.Y, DefaultcameraPan.Z);
        

        KeyboardState currentKeyboardState = new KeyboardState();
        GamePadState currentGamePadState   = new GamePadState();
        GamePadState oldGamePadState       = new GamePadState();


        
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            bWaitingToAnimEnd = false;
            this.IsFixedTimeStep = false;
            //Components.Add(new FrameRateCounter(this));
        }


        #region XNA.Game

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            testReflexion = new TestReflexion();

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("ArialSpriteFont");

            // Load our model
            initialModel = Content.Load<Model>("Models/trooper/trooper");
            //initialModel = Content.Load<Model>("Models/hellknight/hellknight");
            //initialModel = Content.Load<Model>("Models/dude/dude");
            
            // Look up our custom skinning information.
            CSkeletonData skinningData = initialModel.Tag as CSkeletonData;
            if (skinningData == null)
                throw new InvalidOperationException
                    ("This model does not contain a SkinningData tag.");

            // Create plugin list
            List<Assembly> PluginList = new List<Assembly>();
            //PluginList.Add(typeof(PluginTest.Pluggin_Test).Assembly);
            // Create xmas from file
            _animationSystem = XMAnimationSystem.LoadFromFile(@".\Content\AnimGraph.xmas", skinningData, PluginList);
            //_animationSystem = XMAnimationSystem.LoadFromFile(@".\Content\DudeAnimGraph.xmas", skinningData, PluginList);
            // Set the reflexion class
            _animationSystem.ReflexionObject = testReflexion;
            // Add animation end event
            _animationSystem.OnAnimEndEvent += OnAnimEndEvent;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {            
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // FPS counter
            // The time since Update was called last
            _fElapsedTime += gameTime.ElapsedGameTime;
            if (_fElapsedTime > TimeSpan.FromSeconds(1))
            {
                _fElapsedTime -= TimeSpan.FromSeconds(1);
                _fFPS = _fFrameCounter;
                _fFrameCounter = 0;
            }

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            HandleInput();
            UpdateCamera(gameTime);

            // Update the animation system
            _animationSystem.Update(gameTime.ElapsedGameTime);
            
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {            
            GraphicsDevice device = graphics.GraphicsDevice;
            device.Clear(Color.CornflowerBlue);

            // Get the skin transforms
            Matrix[] bones = _animationSystem.GetSkinTransforms();
            float aspectRatio = (float)device.Viewport.Width /
                                (float)device.Viewport.Height;

            // Compute camera matrices.
            Matrix view = Matrix.CreateTranslation(cameraPan) *
                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance),
                                              new Vector3(0, 0, 0), Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    aspectRatio,
                                                                    1,
                                                                    10000);

            // Render the skinned mesh.           
            foreach (ModelMesh mesh in initialModel.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    // Set the skining data
                    foreach (EffectParameter param in effect.Parameters)
                    {
                        if (param.Name == "Bones")
                        {
                            param.SetValue(bones);                            
                        }
                    }
                    //effect.Parameters["Bones"].SetValue(bones);
                    effect.Parameters["View"].SetValue(view);
                    effect.Parameters["Projection"].SetValue(projection);
                }
                mesh.Draw();
            }
            DrawText();
            
            base.Draw(gameTime);
        }

        void DrawText()
        {
            // FPS
            _fFrameCounter++;
            string sFPS = string.Format("FPS: {0}", _fFPS);

            // Text
            string sMessage = "You are ";
            switch (testReflexion.State)
            {
                case TestReflexion.TestState.State_Alive:
                    sMessage += "alive, press button A or Space to dead";
                    break;
                case TestReflexion.TestState.State_Dead:
                    if (bWaitingToAnimEnd)
                        sMessage += "falling to ground";
                    else
                        sMessage += "dead, press button A or space to revive";
                    break;
                case TestReflexion.TestState.State_Reviving:
                    sMessage += "reviving";
                    break;
            }
            spriteBatch.Begin(
                SpriteBlendMode.AlphaBlend, //Blend if you want or use one of the other states
                SpriteSortMode.Deferred,    //Select the proper sorting
                SaveStateMode.SaveState);
            spriteBatch.DrawString(spriteFont, sMessage, new Vector2(20, 10), Color.Black);
            spriteBatch.DrawString(spriteFont, sMessage, new Vector2(19, 9), Color.White);

            spriteBatch.DrawString(spriteFont, sFPS, new Vector2(20, 33), Color.Black);
            spriteBatch.DrawString(spriteFont, sFPS, new Vector2(19, 32), Color.White);
            spriteBatch.End();
        }


        #endregion


        #region Handle Input

        /// <summary>
        /// Handles input for quitting the game.
        /// </summary>
        private void HandleInput()
        {
            currentKeyboardState = Keyboard.GetState();
            currentGamePadState = GamePad.GetState(PlayerIndex.One);

            // Check for exit.
            if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
                currentGamePadState.Buttons.Back == ButtonState.Pressed)
            {
                Exit();
            }

            // Update reflexion values
            // Update speed
            if (testReflexion.State == TestReflexion.TestState.State_Alive)
                testReflexion.Speed = currentGamePadState.ThumbSticks.Left.Y;
            else
                testReflexion.Speed = 0;

            // Update state change, button A pressed and we arn't waiting animation end
            if (!bWaitingToAnimEnd && ((oldGamePadState.Buttons.A == ButtonState.Pressed && currentGamePadState.Buttons.A == ButtonState.Released) || currentKeyboardState.IsKeyDown(Keys.Space)) )
            {
                if (testReflexion.State == TestReflexion.TestState.State_Alive)
                {
                    // Go to dead
                    testReflexion.State = TestReflexion.TestState.State_Dead;
                    bWaitingToAnimEnd = true;
                }
                else if (testReflexion.State == TestReflexion.TestState.State_Dead)
                {
                    // Go to Reviving
                    testReflexion.State = TestReflexion.TestState.State_Reviving;
                    bWaitingToAnimEnd = true;
                }
            }

            oldGamePadState = currentGamePadState;
        }

        /// <summary>
        /// Handles camera input.
        /// </summary>
        private void UpdateCamera(GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

            // Check for input to rotate the camera up and down around the model.
            if (currentKeyboardState.IsKeyDown(Keys.Up))
                cameraArc += time * 0.25f;

            if (currentKeyboardState.IsKeyDown(Keys.Down))
                cameraArc -= time * 0.25f;
            cameraArc += currentGamePadState.ThumbSticks.Right.Y * time * 0.25f;

            // Limit the arc movement.
            if (cameraArc > 90.0f)
                cameraArc = 90.0f;
            else if (cameraArc < -90.0f)
                cameraArc = -90.0f;

            // Pan
            if (currentKeyboardState.IsKeyDown(Keys.W))
                cameraPan.Y += time * 0.1f;
            if (currentKeyboardState.IsKeyDown(Keys.S))
                cameraPan.Y -= time * 0.1f;
            if (currentKeyboardState.IsKeyDown(Keys.D))
                cameraPan.X += time * 0.1f;
            if (currentKeyboardState.IsKeyDown(Keys.A))
                cameraPan.X -= time * 0.1f;
            if (currentGamePadState.DPad.Left == ButtonState.Pressed)
                cameraPan.X -= time * 0.1f;
            if (currentGamePadState.DPad.Right == ButtonState.Pressed)
                cameraPan.X += time * 0.1f;
            if (currentGamePadState.DPad.Up == ButtonState.Pressed)
                cameraPan.Y += time * 0.1f;
            if (currentGamePadState.DPad.Down == ButtonState.Pressed)
                cameraPan.Y -= time * 0.1f;


            // Check for input to rotate the camera around the model.
            if (currentKeyboardState.IsKeyDown(Keys.Right))
                cameraRotation += time * 0.25f;
            if (currentKeyboardState.IsKeyDown(Keys.Left))
                cameraRotation -= time * 0.25f;
            cameraRotation += currentGamePadState.ThumbSticks.Right.X * time * 0.25f;


            // Check for input to zoom camera in and out.
            if (currentKeyboardState.IsKeyDown(Keys.Z))
                cameraDistance += time * 0.2f;
            if (currentKeyboardState.IsKeyDown(Keys.X))
                cameraDistance -= time * 0.2f;
            cameraDistance += currentGamePadState.Triggers.Left * time * 0.2f;
            cameraDistance -= currentGamePadState.Triggers.Right * time * 0.2f;

            // Limit the camera distance.
            if (cameraDistance > 1000.0f)
                cameraDistance = 1000.0f;
            else if (cameraDistance < 40.0f)
                cameraDistance = 40.0f;

            // Reset view
            if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
                currentGamePadState.Buttons.LeftStick == ButtonState.Pressed ||
                currentKeyboardState.IsKeyDown(Keys.R))
            {
                cameraArc = DefaultcameraArc;
                cameraRotation = DefaultcameraRotation;
                cameraDistance = DefaultcameraDistance;
                cameraPan = new Vector3(DefaultcameraPan.X, DefaultcameraPan.Y, DefaultcameraPan.Z);

            }
        }

        #endregion


        #region Events

        public void OnAnimEndEvent(string NodeName, string AnimName, bool bLoop)
        {
            if (NodeName == "reviving")
            {
                testReflexion.State = TestReflexion.TestState.State_Alive;
                bWaitingToAnimEnd = false;
            }
            else if (NodeName == "death")
            {                
                bWaitingToAnimEnd = false;
            }
        }

        #endregion
    }
}
